﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using magic8ballUtility;
using System.IO;
using System.Reflection;

namespace GeneratorLeksickogAnalizatora
{
    class Program
    {
        private static string imeIzvorneDatoteke = null;
        private static string imeIzlazneDatoteke = "output/LeksickiAnalizatorGENERIRANO.cs";
        
        static void Main(string[] args)
        {
            UcitajUlazneParametre(args);

            IUlaznaDatoteka ulaz = new UlaznaDatoteka(imeIzvorneDatoteke);

            Dictionary<string, LijeviKontekst> tablica = new Dictionary<string, LijeviKontekst>();
            
            string pocetniLijeviKontekst = UcitajStanjaLeksickogAnalizatora(ulaz, tablica);

            UcitajPravilaLeksickogAnalizatora(ulaz, tablica);

            if (Directory.Exists("output"))
                Directory.Delete("output", true);
            Directory.CreateDirectory("output");

            ZapisiUIzlaznuDatoteku(imeIzvorneDatoteke, tablica, pocetniLijeviKontekst);

            ZapisiOstaleDatoteke();

            return;
        }

        

        /// <summary>
        /// Učitava ulazne parametre (argumenti komandne linije). Ukoliko se
        /// dogodi pogreška ispisuje poruku o pogrešci i izlazi iz programa
        /// </summary>
        /// <param name="args">argumenti komandne linije</param>
        private static void UcitajUlazneParametre(string[] args)
        {
            if (args.Length % 2 != 1)
            {
                IspisiPorukuOPogresci();
                Environment.Exit(-1);
            }

            for (int i = 0; i < args.Length - 1; i++)
            {
                if (args[i].Equals("-o"))
                {
                    i++;
                    imeIzlazneDatoteke = args[i];
                }
            }
            imeIzvorneDatoteke = args[args.Length - 1];
        }

        /// <summary>
        /// Ispisuje poruku o pogrešci prilikom učitavanja argumenata komandne linije
        /// </summary>
        private static void IspisiPorukuOPogresci()
        {
            Console.Error.WriteLine("Program nije pozvan na ispravan način");
            Console.Error.WriteLine("Primjeri pozivanja programa:");
            Console.Error.WriteLine("\tGeneratorLeksickogAnalizatora.exe definicija.txt");
            Console.Error.WriteLine("\t\t- generira program za ulaznu datoteku definicija.txt");
            Console.Error.WriteLine("\tGeneratorLeksickogAnalizatora.exe -o DefinicijaLeksickogAnalizatora.cs definicija.txt");
            Console.Error.WriteLine("\t\t- generirani program sprema u datoteku DefinicijaLeksickogAnalizatora.cs");
        }

        

        /// <summary>
        /// Prolazi kroz ulaznu datoteku dok ne dođe retka koji počinje sa %X iz kojeg
        /// čita stanja leksičkog analizatora i sprema ih u tablicu
        /// </summary>
        /// <param name="ulaz">ulazna datoteka</param>
        /// <param name="tablica">tablica s podacima o leksičkom analizatoru</param>
        /// <returns>ime početnog stanja analizatora</returns>
        private static string UcitajStanjaLeksickogAnalizatora(IUlaznaDatoteka ulaz, Dictionary<string, LijeviKontekst> tablica)
        {
            string redak = null;
            while (true)
            {
                redak = ulaz.DohvatiRed();
                if (redak.Substring(0, 2).Equals("%X"))
                {
                    var dijelovi = redak.Split(' ');
                    for (int i = 1; i < dijelovi.Length; i++)
                    {
                        (tablica[dijelovi[i]]=new LijeviKontekst()).Pravila = new List<Pravilo>();
                    }
                    ulaz.DohvatiRed();
                    return dijelovi[1];
                }
            }
        }

        /// <summary>
        /// U tablicu učitava pravila leksičkog analizatora
        /// </summary>
        /// <param name="ulaz">ulazna datoteka</param>
        /// <param name="tablica">tablica s podacima o leksičkom analizatoru</param>
        private static void UcitajPravilaLeksickogAnalizatora(IUlaznaDatoteka ulaz, Dictionary<string, LijeviKontekst> tablica)
        {
            string redak = null;
            Dictionary<string, int> maksimalniPrioriteti = new Dictionary<string, int>();
            foreach (var lijeviKontekst in tablica.Keys)
            {
                maksimalniPrioriteti[lijeviKontekst] = 0;
            }

            while (true)
            {
                redak = ulaz.DohvatiRed();
                if (redak == null || redak.Equals(String.Empty))
                    break;

                var dijelovi = redak.Split('>');
                Pravilo pravilo = new Pravilo();
                var regex = redak.Substring(redak.IndexOf('>')+1).Replace(@"\n", "\n").Replace(@"\t", "\t");
                pravilo.RegexNFA = new RegexToNFA(regex).DohvatiAutomat();
                pravilo.Prioritet = maksimalniPrioriteti[dijelovi[0].Substring(1)]++;
                tablica[dijelovi[0].Substring(1)].Pravila.Add(pravilo);
                tablica[dijelovi[0].Substring(1)].PocetnaStanja.Add(pravilo.RegexNFA.ElementAt(0));

                ulaz.DohvatiRed();

                pravilo.ArgumentiAkcije["imeTokena"] = ulaz.DohvatiRed();
                pravilo.ArgumentiAkcije["noviRedak"] = "false";
                pravilo.ArgumentiAkcije["imeStanja"] = null;
                pravilo.ArgumentiAkcije["naZnak"] = "-1";

                while (true)
                {
                    redak = ulaz.DohvatiRed();
                    if (redak[0] == '}') break;
                    if (redak.Contains("NOVI_REDAK"))
                    {
                        pravilo.ArgumentiAkcije["noviRedak"] = "true";
                    }
                    if (redak.Contains("UDJI_U_STANJE"))
                    {
                        pravilo.ArgumentiAkcije["imeStanja"] = redak.Split(' ')[1];
                    }
                    if (redak.Contains("VRATI_SE"))
                    {
                        pravilo.ArgumentiAkcije["naZnak"] = redak.Split(' ')[1];
                    }
                }
            }
        }

        /// <summary>
        /// Zapisuje učitana pravila leksičkog analizatora u datoteku sa definicijom
        /// </summary>
        /// <param name="imeIzvorneDatoteke">ime datoteke iz koje je pročitana definicija</param>
        /// <param name="tablica">struktura sa pravilima analizatora</param>
        /// <param name="imeNoveDatoteke">ime datoteke u koju se zapisuje</param>
        /// <param name="pocetniLijeviKontekst">pocetni lijevi kontekst</param>
        private static void ZapisiUIzlaznuDatoteku(string imeIzvorneDatoteke, Dictionary<string, LijeviKontekst> tablica, string pocetniLijeviKontekst)
        {
            using (TextWriter izlaz = new StreamWriter(imeIzlazneDatoteke))
            {
                izlaz.WriteLine("using System.Collections.Generic;");
                izlaz.WriteLine();
                izlaz.WriteLine("namespace LeksickiAnalizator");
                izlaz.WriteLine("{");
                izlaz.WriteLine("\t/// <summary>");
                izlaz.WriteLine("\t/// Generirani dio definicije");
                izlaz.WriteLine("\t/// </summary>");
                izlaz.WriteLine("\t/// <remarks>Generirano iz datoteke " + imeIzvorneDatoteke + "</remarks>");
                izlaz.WriteLine("\tpublic partial class LeksickiAnalizator");
                izlaz.WriteLine("\t{");
                izlaz.WriteLine("\t\tpublic void Init()");
                izlaz.WriteLine("\t\t{");
                izlaz.WriteLine("\t\t\tpocetniLijeviKontekst=\"" + pocetniLijeviKontekst + "\";");
                izlaz.WriteLine();
                izlaz.WriteLine("\t\t\ttablicaPrijelaza = new Dictionary<string, IDictionary<string, Stanje>>();");
                izlaz.WriteLine("\t\t\ttablicaPocetnihStanja = new Dictionary<string, ISet<string>>();");
                izlaz.WriteLine();

                foreach (string lijeviKontekst in tablica.Keys)
                {
                    // za svaki lijevi kontekst
                    izlaz.WriteLine("\t\t\ttablicaPrijelaza[\"" + lijeviKontekst + "\"]=new Dictionary<string,Stanje>();");
                    izlaz.WriteLine("\t\t\ttablicaPocetnihStanja[\"" + lijeviKontekst + "\"]=new HashSet<string>();");
                    izlaz.WriteLine();
                    int prefiksPravila = 0;
                    foreach (Pravilo pravilo in tablica[lijeviKontekst].Pravila)
                    {
                        // za svako pravilo
                        izlaz.WriteLine("\t\t\ttablicaPocetnihStanja[\"" + lijeviKontekst + "\"].Add(\"" + prefiksPravila + "_" + pravilo.RegexNFA.ElementAt(0).Id + "\");");
                        izlaz.WriteLine();
                        foreach (var stanje in pravilo.RegexNFA)
                        {
                            // za svako stanje
                            izlaz.WriteLine("\t\t\ttablicaPrijelaza[\"" + lijeviKontekst + "\"][\"" + prefiksPravila + "_" + stanje.Id + "\"] = new Stanje(){");

                            izlaz.WriteLine("\t\t\t\tPrijelazi = new Dictionary<char,ISet<string>>(){");
                            var prijelazi = stanje.DohvatiPrijelaze();
                            var brojPrijelaza = prijelazi.Count();
                            if (brojPrijelaza > 0)
                            {
                                for (int i = 0; i < brojPrijelaza; i++)
                                {
                                    // dodaje sve prijelaze
                                    char znakPrijelaza = prijelazi.ElementAt(i).Key;
                                    string zapisPrijelaza = znakPrijelaza.ToString();
                                    if (znakPrijelaza == '\n')
                                        zapisPrijelaza = @"\n";
                                    if (znakPrijelaza == '\t')
                                        zapisPrijelaza = @"\t";
                                    if (znakPrijelaza == '\\')
                                        zapisPrijelaza = @"\\";
                                    if (znakPrijelaza == '\'')
                                        zapisPrijelaza = @"\'";

                                    izlaz.Write("\t\t\t\t\t{ '" + zapisPrijelaza  + "', new HashSet<string>(){ ");
                                    ISet<int> indeksiSljedStanja = prijelazi.ElementAt(i).Value;
                                    for (int j = 0; j < indeksiSljedStanja.Count; j++)
                                    {
                                        // dodaje sve indekse sljedećeg stanja u prijelaz
                                        if (j != indeksiSljedStanja.Count - 1)
                                            izlaz.Write('"' + prefiksPravila.ToString() + "_" + indeksiSljedStanja.ElementAt(j) + "\", ");
                                        else
                                            izlaz.Write('"' + prefiksPravila.ToString() + "_" + indeksiSljedStanja.ElementAt(j) + "\"");
                                    }
                                    if (i!=brojPrijelaza-1)
                                        izlaz.WriteLine(" } },");
                                    else 
                                        izlaz.WriteLine(" } }");
                                }
                            }
                            izlaz.WriteLine("\t\t\t\t},");
                            if (stanje.Prihvatljivo)
                            {
                                izlaz.WriteLine("\t\t\t\tArgumentiAkcije=new Dictionary<string,string>(){");
                                var kljucevi = pravilo.ArgumentiAkcije.Keys;
                                for (int i = 0; i < kljucevi.Count; i++)
                                {
                                    // dodaje sve argumente akcije
                                    if (i != kljucevi.Count - 1)
                                        izlaz.WriteLine("\t\t\t\t\t{ \"" + kljucevi.ElementAt(i) + "\",\"" + pravilo.ArgumentiAkcije[kljucevi.ElementAt(i)] + "\" },");
                                    else
                                        izlaz.WriteLine("\t\t\t\t\t{ \"" + kljucevi.ElementAt(i) + "\",\"" + pravilo.ArgumentiAkcije[kljucevi.ElementAt(i)] + "\" }");
                                }
                                izlaz.WriteLine("\t\t\t\t},");
                            }
                            if (stanje.Prihvatljivo)
                            {
                                izlaz.WriteLine("\t\t\t\tPrioritet=" + pravilo.Prioritet + ',');
                            }
                            izlaz.WriteLine("\t\t\t\tJePrihvatljivo=" + (stanje.Prihvatljivo ? "true" : "false"));
                            izlaz.WriteLine("\t\t\t};");
                        }
                        prefiksPravila++;
                        izlaz.WriteLine();
                    }
                }

                izlaz.WriteLine("\t\t}");
                izlaz.WriteLine("\t}");
                izlaz.WriteLine("}");
            }
        }

        private static void ZapisiOstaleDatoteke()
        {
            ZapisiStreamNaDisk("LeksickiAnalizator.cs", System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("GeneratorLeksickogAnalizatora.template.LeksickiAnalizator.cs"));
            ZapisiStreamNaDisk("ListaUniformnihZnakova.cs", System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("GeneratorLeksickogAnalizatora.template.PodatkovneStrukture.ListaUniformnihZnakova.cs"));
            ZapisiStreamNaDisk("TablicaIzvornihZnakova.cs", System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("GeneratorLeksickogAnalizatora.template.PodatkovneStrukture.TablicaIzvornihZnakova.cs"));
            ZapisiStreamNaDisk("Pogreska.cs", System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("GeneratorLeksickogAnalizatora.template.PodatkovneStrukture.Pogreska.cs"));
        }

        private static void ZapisiStreamNaDisk(string outputFileName, Stream inputStream)
        {
            if (inputStream != null)
            {

                byte[] buffer = new byte[inputStream.Length];

                inputStream.Read(buffer, 0, (int)(inputStream.Length));
            
                using (var sw = new BinaryWriter(File.Open(@"output/" + outputFileName, FileMode.Create)))
                {
                    sw.Write(buffer);

                    sw.Flush();

                }

            }
        }

    }

    /// <summary>
    /// Lijevi kontekst leksičkog analizatora
    /// </summary>
    public class LijeviKontekst
    {
        public LijeviKontekst()
        {
            PocetnaStanja = new HashSet<magic8ballUtility.RegexToNFA.Stanje>();
        }

        /// <summary>
        /// Dozvoljena pravila u danom lijevom kontekstu
        /// </summary>
        public IList<Pravilo> Pravila { get; set; }

        /// <summary>
        /// Skup početnih stanja regularnih izraza u danom lijevom kontekstu
        /// </summary>
        public ISet<magic8ballUtility.RegexToNFA.Stanje> PocetnaStanja { get; set; }
    }

    /// <summary>
    /// Pravilo leksičkog analizatora
    /// </summary>
    public class Pravilo
    {
        public Pravilo()
        {
            ArgumentiAkcije = new Dictionary<string, string>();
        }

        /// <summary>
        /// Lista stanja epsilon NKA koji prihvaća nizove opisane regularnim izrazom
        /// pravila
        /// </summary>
        public IList<magic8ballUtility.RegexToNFA.Stanje> RegexNFA { get; set; }

        /// <summary>
        /// Argumenti akcije
        /// </summary>
        public IDictionary<string, string> ArgumentiAkcije { get; set; }

        /// <summary>
        /// Prioritet pravila
        /// </summary>
        public int Prioritet { get; set; }
    }
}
